package com.tripcube.ai.service.impl;

import com.aliyun.bailian20231229.Client;
import com.aliyun.bailian20231229.models.*;
import com.aliyun.teautil.models.RuntimeOptions;
import com.tripcube.ai.model.dto.AddFileDTO;
import com.tripcube.ai.model.entity.Document;
import com.tripcube.ai.service.DocumentService;
import com.tripcube.common.core.exception.ServiceException;
import com.tripcube.common.core.web.page.TableDataInfo;
import com.tripcube.system.api.RemoteFileService;
import jakarta.annotation.Resource;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.io.DataOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * 文档管理服务实现类
 *
 * @author tripcube
 */
@Service
@RequiredArgsConstructor
@Slf4j
public class DocumentServiceImpl implements DocumentService {

    private final Client bailianClient;

    @Resource
    private RemoteFileService fileService;

    @Value("${alibaba.cloud.workspace-id}")
    private String workspaceId;

    /**
     * 查询文档列表
     */
    @Override
    public TableDataInfo selectDocumentList(String categoryId, String fileName, String nextToken, Integer maxResults) {
        try {
            // 构建查询请求
            ListFileRequest request = new ListFileRequest()
                    .setCategoryId(categoryId)
                    .setFileName(fileName)
                    .setNextToken(nextToken)
                    .setMaxResults(maxResults);

            RuntimeOptions runtime = new RuntimeOptions();

            // 调用阿里百炼API查询文档列表
            ListFileResponse response = bailianClient.listFileWithOptions(
                    workspaceId, request, new HashMap<>(), runtime);

            if (response.getBody() == null || response.getBody().getData() == null) {
                return new TableDataInfo(new ArrayList<>(), 0);
            }

            return new TableDataInfo(response.getBody().getData().getFileList(), response.getBody().getData().getTotalCount());

        } catch (Exception e) {
            log.error("查询文档列表失败", e);
            throw new ServiceException("查询文档列表失败: " + e.getMessage());
        }
    }

    /**
     * 添加文档
     *
     * @param addFileDTO 添加文档参数
     * @return 上传后的文档信息
     */
    @Override
    public AddFileResponseBody.AddFileResponseBodyData addDocument(AddFileDTO addFileDTO) {
        try {
            String url = addFileDTO.getOriginalFileUrl();
            String fileName = addFileDTO.getFileName();
            // 1. 申请文件上传租约
            ApplyFileUploadLeaseResponseBody.ApplyFileUploadLeaseResponseBodyData leaseData =
                    applyUploadLease(fileName, getFileMD5(url), getFileSize(url));

            // 2. 上传文件到临时存储
            uploadFileLinkWithLease(leaseData.getParam().getUrl(), addFileDTO.getOriginalFileUrl(), leaseData);

            AddFileRequest addFileRequest = new AddFileRequest()
                    .setLeaseId(leaseData.getFileUploadLeaseId())
                    .setParser(addFileDTO.getParser())
                    .setCategoryId(addFileDTO.getCategoryId())
                    .setTags(addFileDTO.getTags()!= null ? addFileDTO.getTags() : null)
                    .setCategoryType(addFileDTO.getCategoryType())
                    .setOriginalFileUrl(leaseData.getParam().getUrl());

            RuntimeOptions runtime = new RuntimeOptions();
            Map<String, String> headers = new HashMap<>();

            AddFileResponse addFileResponse = bailianClient.addFileWithOptions(
                    workspaceId, addFileRequest, headers, runtime);

            if (addFileResponse.getBody() == null || addFileResponse.getBody().getData() == null) {
                throw new ServiceException("添加文件到数据中心失败");
            }
            AddFileResponseBody.AddFileResponseBodyData data = addFileResponse.getBody().getData();

            log.info("成功添加文档到数据中心，文件ID: {}", data.fileId);
            return data;

        } catch (Exception e) {
            log.error("添加文档失败", e);
            throw new ServiceException("添加文档失败: " + e.getMessage());
        }
    }

    /**
     * 申请文件上传租约
     * @param fileName 文件名
     * @param md5 文件MD5值
     * @param sizeInBytes 文件大小（字节）
     * @return 包含上传URL和请求头的Map
     */
    @Override
    public ApplyFileUploadLeaseResponseBody.ApplyFileUploadLeaseResponseBodyData applyUploadLease(String fileName, String md5, String sizeInBytes) {
        try {
            ApplyFileUploadLeaseRequest leaseRequest = new ApplyFileUploadLeaseRequest()
                    .setFileName(fileName)
                    .setMd5(md5)
                    .setSizeInBytes(sizeInBytes);

            RuntimeOptions runtime = new RuntimeOptions();
            Map<String, String> headers = new HashMap<>();

            // 阿里百炼 API 要求分类类型
            String categoryType = "default";
            ApplyFileUploadLeaseResponse leaseResponse = bailianClient.applyFileUploadLeaseWithOptions(
                    categoryType, workspaceId, leaseRequest, headers, runtime);

            if (leaseResponse.getBody() == null || leaseResponse.getBody().getData() == null) {
                throw new ServiceException("申请文件上传租约失败");
            }

            ApplyFileUploadLeaseResponseBody.ApplyFileUploadLeaseResponseBodyData result = leaseResponse.getBody().getData();

            return result;

        } catch (Exception e) {
            log.error("申请文件上传租约失败", e);
            throw new ServiceException("申请文件上传租约失败: " + e.getMessage());
        }
    }

    /**
     * 删除文档
     * @param fileId 文档ID
     * @return 删除是否成功
     */
    @Override
    public boolean deleteDocument(String fileId) {
        try {
            DeleteFileResponse response = bailianClient.deleteFileWithOptions(workspaceId, fileId, new HashMap<>(), new RuntimeOptions());
            return response.getBody().getSuccess();
        } catch (Exception e) {
            log.error("删除文档失败，ID: {}", fileId, e);
            throw new ServiceException("删除文档失败: " + e.getMessage());
        }
    }

    /**
     * 批量删除文档
     * @param fileIds 文档ID列表
     * @return 删除的文档数量
     */
    @Override
    public int deleteDocuments(List<String> fileIds) {
        try {
            int count = 0;
            for (String fileId : fileIds) {
                if (deleteDocument(fileId)) {
                    count ++;
                }
            }
            return count;
        } catch (Exception e) {
            log.error("删除文档失败", e);
            throw new ServiceException("删除文档失败: " + e.getMessage());
        }
    }

    /**
     * 查询文档状态
     *
     * @param fileId 文档ID
     * @return 文档详细信息
     */
    @Override
    public DescribeFileResponseBody.DescribeFileResponseBodyData selectDocumentById(String fileId) {
        try {
            DescribeFileResponse response = bailianClient.describeFileWithOptions(workspaceId, fileId, new HashMap<>(), new RuntimeOptions());
            if (response.getBody() == null || response.getBody().getData() == null) {
                return null;
            }
            return response.getBody().getData();
        } catch (Exception e) {
            log.error("查询文档失败，ID: {}", fileId, e);
            throw new ServiceException("查询文档失败: " + e.getMessage());
        }
    }

    /**
     * 更新文档标签
     */
    @Override
    public boolean updateDocumentTags(String fileId, List<String> tags) {
        try {
            UpdateFileTagRequest request = new UpdateFileTagRequest().setTags(tags);
            UpdateFileTagResponse response = bailianClient.updateFileTagWithOptions(
                    workspaceId, fileId, request, new HashMap<>(), new RuntimeOptions());
            return response.getBody().getSuccess();
        } catch (Exception e) {
            log.error("更新文档标签失败，ID: {}", fileId, e);
            throw new ServiceException("更新文档标签失败: " + e.getMessage());
        }
    }

    /**
     * 从授权OSS导入文件
     */
    @Override
    public boolean addFilesFromOss(String categoryId, String ossRegionId, String ossBucketName, List<Map<String, String>> fileDetails, List<String> tags) {
        try {
            AddFilesFromAuthorizedOssRequest request = new AddFilesFromAuthorizedOssRequest();

            List<AddFilesFromAuthorizedOssRequest.AddFilesFromAuthorizedOssRequestFileDetails> fileDetail = new ArrayList<>();
            for(Map<String, String> detail : fileDetails){
                AddFilesFromAuthorizedOssRequest.AddFilesFromAuthorizedOssRequestFileDetails requestFileDetail = new AddFilesFromAuthorizedOssRequest.AddFilesFromAuthorizedOssRequestFileDetails()
                        .setFileName(detail.get("fileName"))
                        .setOssKey(detail.get("ossKey"));
                fileDetail.add(requestFileDetail);
            }
            request.setFileDetails(fileDetail);

            AddFilesFromAuthorizedOssResponse response = bailianClient.addFilesFromAuthorizedOssWithOptions(
                    workspaceId, request, new HashMap<>(), new RuntimeOptions());

            return response.getBody().getSuccess().equals("true");
        } catch (Exception e) {
            log.error("从OSS导入文件失败", e);
            throw new ServiceException("从OSS导入文件失败: " + e.getMessage());
        }
    }

    /**
     * 轮询文档状态
     */
    private void waitForDocumentStatus(String fileId) throws Exception {
        Document document = new Document();
        int maxAttempts = 60; // 5分钟超时
        int attempt = 0;

        while (attempt < maxAttempts) {
            DescribeFileResponse response = bailianClient.describeFileWithOptions(workspaceId, fileId, new HashMap<>(), new RuntimeOptions());
            DescribeFileResponseBody.DescribeFileResponseBodyData data = response.getBody().getData();

            document.setFileId(fileId);
            document.setFileName(data.getFileName());
            document.setFileType(data.getFileType());
            document.setSizeInBytes(data.getSizeInBytes());
            document.setStatus(data.getStatus());
            document.setCreateTime(LocalDateTime.parse(data.getCreateTime()));
            document.setParser(data.getParser());

            if ("PARSE_SUCCESS".equals(data.getStatus())) {
                log.info("文档解析完成，文件ID: {}", fileId);
                break;
            } else if ("INIT".equals(data.getStatus()) || "PARSING".equals(data.getStatus())) {
                log.info("文档状态: {}，等待解析...", data.getStatus());
                TimeUnit.SECONDS.sleep(5);
            } else {
                log.error("文档解析失败，状态: {}", data.getStatus());
                throw new ServiceException("文档解析失败，状态: " + data.getStatus());
            }
            attempt++;
        }

        if (attempt >= maxAttempts) {
            throw new ServiceException("文档解析超时，文件ID: " + fileId);
        }
    }

    /**
     * 上传URL文档到临时存储
     */
    private void uploadFileLinkWithLease(String preSignedUrl, String sourceUrlString,
                                         ApplyFileUploadLeaseResponseBody.ApplyFileUploadLeaseResponseBodyData leaseData) {
        HttpURLConnection connection = null;
        HttpURLConnection sourceConnection = null;
        try {
            // 创建上传URL对象
            URL url = new URL(preSignedUrl);
            connection = (HttpURLConnection) url.openConnection();

            // 设置请求方法
            connection.setRequestMethod(leaseData.getParam().getMethod());

            // 设置请求头
            Object headersObj = leaseData.getParam().getHeaders();
            if (headersObj instanceof Map) {
                @SuppressWarnings("unchecked")
                Map<String, String> headers = (Map<String, String>) headersObj;
                for (Map.Entry<String, String> entry : headers.entrySet()) {
                    connection.setRequestProperty(entry.getKey(), entry.getValue());
                }
            }

            // 允许向connection输出
            connection.setDoOutput(true);

            // 打开源文件URL连接
            URL sourceUrl = new URL(sourceUrlString);
            sourceConnection = (HttpURLConnection) sourceUrl.openConnection();
            sourceConnection.setRequestMethod("GET");

            // 检查源文件是否可访问
            int sourceFileResponseCode = sourceConnection.getResponseCode();
            if (sourceFileResponseCode != HttpURLConnection.HTTP_OK) {
                throw new ServiceException("无法获取源文件，响应码: " + sourceFileResponseCode);
            }

            // 从URL读取文档并通过连接上传
            try (DataOutputStream outStream = new DataOutputStream(connection.getOutputStream());
                 java.io.InputStream in = new java.io.BufferedInputStream(sourceConnection.getInputStream())) {
                byte[] buffer = new byte[4096];
                int bytesRead;
                while ((bytesRead = in.read(buffer)) != -1) {
                    outStream.write(buffer, 0, bytesRead);
                }
                outStream.flush();
            }

            // 检查上传响应
            int responseCode = connection.getResponseCode();
            if (responseCode == HttpURLConnection.HTTP_OK) {
                log.info("文件通过链接上传成功");
            } else {
                throw new ServiceException("文件上传失败，响应码: " + responseCode);
            }
        } catch (Exception e) {
            log.error("通过链接上传文件过程中发生错误", e);
            throw new ServiceException("文件上传失败: " + e.getMessage());
        } finally {
            if (connection != null) {
                connection.disconnect();
            }
            if (sourceConnection != null) {
                sourceConnection.disconnect();
            }
        }
    }


    /**
     * 上传本地文档到临时存储
     */
    private void uploadFileWithLease(String preSignedUrl, String filePath,
                                     ApplyFileUploadLeaseResponseBody.ApplyFileUploadLeaseResponseBodyData leaseData) {

        HttpURLConnection connection = null;
        try {
            // 创建URL对象
            URL url = new URL(preSignedUrl);
            connection = (HttpURLConnection) url.openConnection();

            // 设置请求方法
            connection.setRequestMethod(leaseData.getParam().getMethod());
            Object headersObj = leaseData.getParam().getHeaders();
            if (headersObj instanceof Map) {
                @SuppressWarnings("unchecked")
                Map<String, String> headers = (Map<String, String>) headersObj;
                for (Map.Entry<String, String> entry : headers.entrySet()) {
                    connection.setRequestProperty(entry.getKey(), entry.getValue());
                }
            }

            // 允许向connection输出
            connection.setDoOutput(true);


            // 读取文件并通过连接上传
            try (DataOutputStream outStream = new DataOutputStream(connection.getOutputStream());
                 FileInputStream fileInputStream = new FileInputStream(filePath)) {
                byte[] buffer = new byte[4096];
                int bytesRead;
                while ((bytesRead = fileInputStream.read(buffer)) != -1) {
                    outStream.write(buffer, 0, bytesRead);
                }
                outStream.flush();
            }

            int responseCode = connection.getResponseCode();
            if (responseCode == HttpURLConnection.HTTP_OK) {
                log.info("文件上传成功");
            } else {
                throw new ServiceException("文件上传失败，响应码: " + responseCode);
            }
        } catch (Exception e) {
            log.error("文件上传过程中发生错误", e);
            throw new ServiceException("文件上传失败: " + e.getMessage());
        } finally {
            if (connection != null) {
                connection.disconnect();
            }
        }
    }

    /**
     * 计算文件的MD5值
     */
    /**
     * 计算文件的MD5值（支持本地文件和远程URL）
     */
    private String getFileMD5(String filePath) throws IOException, NoSuchAlgorithmException {
        MessageDigest md = MessageDigest.getInstance("MD5");

        if (filePath.startsWith("http://") || filePath.startsWith("https://")) {
            // 处理远程URL文件
            try (java.io.InputStream is = new URL(filePath).openStream()) {
                byte[] buffer = new byte[8192];
                int bytesRead;
                while ((bytesRead = is.read(buffer)) != -1) {
                    md.update(buffer, 0, bytesRead);
                }
            }
        } else {
            // 处理本地文件
            try (FileInputStream fis = new FileInputStream(filePath)) {
                byte[] buffer = new byte[8192];
                int bytesRead;
                while ((bytesRead = fis.read(buffer)) != -1) {
                    md.update(buffer, 0, bytesRead);
                }
            }
        }

        byte[] digest = md.digest();

        // 将字节数组转换为十六进制字符串
        StringBuilder sb = new StringBuilder();
        for (byte b : digest) {
            sb.append(String.format("%02x", b));
        }
        return sb.toString();
    }


    /**
     * 获取文件大小（字节）（支持本地文件和远程URL）
     */
    private String getFileSize(String filePath) throws IOException {
        if (filePath.startsWith("http://") || filePath.startsWith("https://")) {
            // 处理远程URL文件
            URL url = new URL(filePath);
            HttpURLConnection connection = (HttpURLConnection) url.openConnection();
            connection.setRequestMethod("HEAD");
            return String.valueOf(connection.getContentLength());
        } else {
            // 处理本地文件
            java.io.File file = new java.io.File(filePath);
            return String.valueOf(file.length());
        }
    }


}